/**
 * @class   wosh::Table
 * @brief   Generic wosh::ISerializable objects container, organized as a 2-dimensions matrix.
 *
 *          Provides also table's headers and name.
 *          Items are stored inside an array (raster-scan), headers are stored
 *          in separated array. First item is [0,0], last is located at [rows-1,cols-1].
 *
 *          Current implementation does not allow to resize the table once created,
 *          but you may only create/reallocate headers.
 *
 * Common snippet of creating a new table:
 * \code

 * \endcode
 *
 *          Tables are heavily used for exporting/exchanging data between services
 *          (and front-ends for example), the WOSH Gui framework provides some
 *          clases/utilities such as wosh::gui::WoshTableModel to render a table
 *          writing few lines of code.
 *
 * A sample snippet of parsing a table (from a Message):
 * \code

 * \endcode
 *
 *
 * @ingroup DataModels
 ****************************************************************************
 * @version $Id: Table.h 2863 2010-08-07 04:28:58Z alex $
 * @author  Alessandro Polo
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

#ifndef __WOSH_Core_Table_H__
 #define __WOSH_Core_Table_H__

 #include <woshDefs.h>
 #include <core/Serialization.h>
 #include <core/Variant.h>
 #include <string>


namespace wosh {

class List;
class Variant;

class Table : public virtual ISerializable
 {
	WOSH_SERIALIZABLE(wosh::Table)
	WOSH_COMPARE_RTTI(wosh::Table)

	public:
		/**
		 * @brief  Empty constructor.
		 */
		Table();
		/**
		 * @brief  Common constructor. Provided for convenience.
		 * @param [in] rows number of rows
		 * @param [in] cols number of columns
		 * @param [in] horizontalHeader when true it will allocate horizontal header (allocHeaderHorizontal)
		 * @param [in] verticalHeader when true it will allocate vertical header (allocHeaderVertical)
		 * @see    Table::alloc(row_count, col_count, horizontalHeader, verticalHeader)
		 */
		Table( unsigned int rows, unsigned int cols, bool horizontalHeader = false, bool verticalHeader = false );
		/**
		 * @brief  Copy constructor.
		 * @param m [in] the source object to be copied
		 */
		Table( const Table& m );
		/**
		 * @brief  Empty destructor. All elements are destroyed.
		 * @see    Table::clear(true)
		 */
		virtual ~Table();


/** @name Getters
 * @{
 ******************************************************************************/
	public:
		/**
		 * @brief  Get the number of rows.
		 * @return return number of rows (always positive)
		 */
		inline unsigned int getRows() const 				{ return this->rows; }
		/**
		 * @brief  Get the number of columns.
		 * @return return number of columns (always positive)
		 */
		inline unsigned int getCols() const 				{ return this->cols; }
		/**
		 * @brief  Get the number of items.
		 * @return return number of items: rows x columns
		 */
		inline unsigned int getCount() const 				{ return (this->rows * this->cols); }

		/**
		 * @brief  Get the selected item of the table.
		 *         First item is located at [0,0], last at [rows-1, cols-1].
		 *         Headers are stored in a different location.
		 * @param [in] col the column of the item to be retrieved [0, cols[
		 * @param [in] row the row of the item to be retrieved [0, rows[
		 * @return pointer to the item, may be NULL
		 * @see    set()
		 */
		inline const ISerializable* get( unsigned int row, unsigned int col ) const {
			return getAt( row * this->cols + col );
		 }
		inline ISerializable* get( unsigned int row, unsigned int col ){
			return getAt( row * this->cols + col );
		 }

		const Variant& getAsVariant( unsigned int row, unsigned int col ) const;

		/**
		 * @brief  Get the selected item of the table.
		 *         Items are ordered in raster scan, headers are stored in a different location.
		 * @param [in] raster_index the raster-index of the item to be retrieved
		 * @return pointer to the item, may be NULL
		 * @see    setAt()
		 */
		const ISerializable* getAt( unsigned int raster_index ) const;
		ISerializable* getAt( unsigned int raster_index );

		/**
		 * @brief  Get the selected item of the horizontal header.
		 * @param [in] col the column of the item to be retrieved [0, cols[
		 * @return pointer to the horizontal header item, may be NULL
		 * @see    setHeaderHorizontal()
		 */
		const ISerializable* getHeaderHorizontal( unsigned int col ) const;
		ISerializable* getHeaderHorizontal( unsigned int col );

		/**
		 * @brief  Get the selected item of the vertical header.
		 * @param [in] row the row of the item to be retrieved [0, rows[
		 * @return pointer to the vertical header item, may be NULL
		 * @see    setHeaderVertical()
		 */
		const ISerializable* getHeaderVertical( unsigned int row ) const;
		ISerializable* getHeaderVertical( unsigned int row );

		/**
		 * @brief  Get the class-name of selected item. Empty when item is NULL
		 * @param [in] row the row of the item to be evaluated [0, rows[
		 * @param [in] col the column of the item to be evaluated [0, cols[
		 * @return string representing the class-name of the item (empty when item is NULL)
		 */
		std::string getClassNameOf( unsigned int row, unsigned int col ) const;
		/**
		 * @brief  Get the class-name of items (of selected column). Empty when there are multiple types.
		 * @param [in] col the column to be evaluated [0, cols[
		 * @return string representing the class-name of all items (empty when they are misaligned/NULL)
		 */
		std::string getClassNameOfCol( unsigned int col ) const;
		/**
		 * @brief  Get the class-name of all items. Empty when there are multiple types.
		 * @return string representing the class-name of all items (empty when they are misaligned/NULL)
		 */
		std::string getClassNameOfItems() const;

		/**
		 * @brief  Evaluate if the table (items) is empty.
		 * @return true if array is null or empty, false when at least one element exists (not NULL).
		 */
		bool isEmpty() const;

		/**
		 * @brief  Evaluate if the table has an horizonal header
		 * @return true if horizontal-header array has been allocated
		 * @see    allocHeaderHorizontal()
		 */
		inline bool hasHeaderHorizontal() const				{ return (this->headerHorizontal != NULL); }
		/**
		 * @brief  Evaluate if the table has an vertical header
		 * @return true if vertical-header array has been allocated
		 * @see    allocHeaderVertical()
		 */
		inline bool hasHeaderVertical() const				{ return (this->headerVertical != NULL); }

		/**
		 * @brief  Get the table name
		 * @return return (constant) reference to the table name
		 * @see    setTableName()
		 */
		inline const std::string& getTableName() const		{ return this->table_name; }
		inline std::string& getTableName()					{ return this->table_name; }


		template <typename STD_CONTAINER>
		int getRow( int row, STD_CONTAINER& container, bool acquire = true ) const {
			if ( row <= -1 ) {
				if ( this->headerHorizontal == NULL ) return -1;
				for(unsigned int col=0; col<this->cols; col++) {
					if ( acquire || this->headerHorizontal[col] == NULL )
						container.push_back( this->headerHorizontal[col] );
					else
						container.push_back( this->headerHorizontal[col]->clone() );
				 }
				return this->cols;
			 }
			if ( row >= (int)this->rows ) return -1;
			for(unsigned int col=0; col<this->cols; col++) {
				if ( acquire || this->items[this->rows*col] == NULL )
					container.push_back( this->items[this->rows*col] );
				else
					container.push_back( this->items[this->rows*col]->clone() );
			 }
			return this->cols;
		 }

//@}
/** @name Setters
 * @{
 ******************************************************************************/
	public:
		/**
		 * @brief  Set name fo the table
		 * @param [in] name the name of the table
		 * @see    getTableName()
		 */
		void setTableName( const std::string& name ) 		{ this->table_name = name; }

		/**
		 * @brief  Allocate table
		 * @param [in] rows number of rows
		 * @param [in] cols number of columns
		 * @param [in] horizontalHeader when true it will allocate horizontal header (allocHeaderHorizontal)
		 * @param [in] verticalHeader when true it will allocate vertical header (allocHeaderVertical)
		 * @return WRET_ERR_INTERNAL when the table is already allocated; WRET_OK else
		 * @see    allocHeaderHorizontal(), allocHeaderVertical(), set(), get()
		 */
		WRESULT alloc( unsigned int rows, unsigned int cols, bool horizontalHeader = false, bool verticalHeader = false );

		/**
		 * @brief  Allocate or free the horizontal header. When header was already allocated, it will released.
		 * @param [in] allocate when true it will allocate heder, false will destroy current
		 * @see    setHeaderHorizontal()
		 */
		void allocHeaderHorizontal( bool allocate );
		/**
		 * @brief  Allocate or free the vertical header. When header was already allocated, it will released.
		 * @param [in] allocate when true it will allocate heder, false will destroy current
		 * @see    setHeaderVertical()
		 */
		void allocHeaderVertical( bool allocate );

		/**
		 * @brief  Set an item of the table 
		 *         First item is located at [0,0], last at [rows-1, cols-1]. Headers are stored in a different location.
		 * @param [in] object the object to be set, ownership will be actuired by the table
		 * @param [in] row the row of the item to be set [0, rows[
		 * @param [in] col the column of the item to be set [0, cols[
		 * @param [in] deletePrevious when true, it will free any previous item (destroy object),
		 *             else previous pointer will be overwritted (lost)
		 * @return WRET_ERR_PARAM when column is invalid or table hasn't been allocated; WRET_OK else
		 * @see    get()
		 */
		WRESULT set( ISerializable* object, unsigned int row, unsigned int col, bool deletePrevious = true );

		// usage: add<Variant>( "asd" ); // no memory waste
		template <class PROXY, typename TYPE>
		WRESULT set( const TYPE& object, unsigned int row, unsigned int col ) {
			return set( new PROXY(object), row, col, true );
		 }

		/**
		 * @brief  Set an item of the table 
		 *         First item is located at [0,0], last at [rows-1, cols-1]. Headers are stored in a different location.
		 * @param [in] object the object to be set, ownership will be actuired by the table
		 * @param [in] raster_index the raster-index of the item to be retrieved (row * COLS + col)
		 * @param [in] deletePrevious when true, it will free any previous item (destroy object),
		 *             else previous pointer will be overwritted (lost)
		 * @return WRET_ERR_PARAM when index is invalid or table hasn't been allocated; WRET_OK else
		 * @see    getAt()
		 */
		WRESULT setAt( ISerializable* object, unsigned int raster_index, bool deletePrevious = true );

		WRESULT setColumn( const List* list, unsigned int col, unsigned int row_start = 0, bool acquireItem = false, bool deletePrevious = true );

		/**
		 * @brief  Set an item of the horizontal header. 
		 * @param [in] object the object to be set, ownership will be actuired by the table
		 * @param [in] col the column of the item [0, cols[
		 * @param [in] deletePrevious when true, it will free any previous item (destroy object),
		 *             else previous pointer will be overwritted (lost)
		 * @return WRET_ERR_PARAM when column is invalid or header hasn't been allocated; WRET_OK else
		 * @see    allocHeaderHorizontal(), getHeaderHorizontal()
		 */
		WRESULT setHeaderHorizontal( ISerializable* object, unsigned int col, bool deletePrevious = true );

		WRESULT setHeaderHorizontal( const Variant& object, unsigned int col, bool deletePrevious = true ) {
			return setHeaderHorizontal( new Variant(object), col, deletePrevious );
		 }

		/**
		 * @brief  Set an item of the vertical header. 
		 * @param [in] object the object to be set, ownership will be actuired by the table
		 * @param [in] row the row of the item [0, rows[
		 * @param [in] deletePrevious when true, it will free any previous item (destroy object),
		 *             else previous pointer will be overwritted (lost)
		 * @return WRET_ERR_PARAM when row is invalid or header hasn't been allocated; WRET_OK else
		 * @see    allocHeaderVertical(), getHeaderVertical()
		 */
		WRESULT setHeaderVertical( ISerializable* object, unsigned int row, bool deletePrevious = true );

		WRESULT setHeaderVertical( const Variant& object, unsigned int row, bool deletePrevious = true ) {
			return setHeaderVertical( new Variant(object), row, deletePrevious );
		 }

		/**
		 * @brief  Clear the table (headers, items)
		 * @param [in] deleteContents when true it will destoy each (cell) item
		 */
		virtual void clear( bool deleteContents = true );

		/**
		 * @brief  Clear the content table (items)
		 * @param [in] deleteContents when true it will destoy each (cell) item
		 */
		virtual void clearContents( bool deleteContents = true );

		/**
		 * @brief  Clear an item of the table. Provided for convenience.
		 * @param [in] row the row of the item to be set [0, rows[
		 * @param [in] col the column of the item to be set [0, cols[
		 * @param [in] deleteContents when true, it will free any previous item (destroy object),
		 *             else previous pointer will be overwritted (lost)
		 * @return WRET_ERR_PARAM when column is invalid or table hasn't been allocated; WRET_OK else
		 * @see    set(NULL, row, col, deleteContents)
		 */
		inline WRESULT clear( unsigned int row, unsigned int col, bool deleteContents = true )
															{ return set((ISerializable*)NULL, row, col, deleteContents); }

	//	int mergeByColumn( const Table* other, int primary_col, short compare_mode = 0, short overwrite_mode = 0 );

//@}
/** @name Search methods
 * @{
 ******************************************************************************/
	public:

		int findRowOfItem( int col, const ISerializable* other, short compare_mode = 0 ) const;
		inline int findRowOfItem( int col, const Variant& other ) const {
			return findRowOfItem(col, &other, 0);
		 }

		int findColumnOfItem( int row, const ISerializable* other, short compare_mode = 0 ) const;
		inline int findColumnOfItem( int row, const Variant& other ) const {
			return findColumnOfItem(row, &other, 0);
		 }

		const ISerializable* findMappedColumnItem( unsigned int col_primary, unsigned int return_column, const ISerializable* other, short compare_mode = 0 ) const;
		inline const Variant& findMappedColumnItem( unsigned int col_primary, unsigned int return_column, const Variant& other ) const {
			const Variant* obj = dynamic_cast<const Variant*>(findMappedColumnItem( col_primary, return_column, &other, 0 ));
			if ( obj == NULL ) return Variant::EmptyVariant;
			return *obj;
		 }

//@}
/** @name Sorting methods
 * @{
 ******************************************************************************/
	public:
		WRESULT swapRow( unsigned int source_index, unsigned int dest_index );

		// very expensive!
		WRESULT sortByColumn( int column, short order );

//@}
/** @name Operators overloading
 * @{
 ******************************************************************************/
	public:
		virtual Table& operator=(const Table& m);
		virtual bool operator==(const Table &other) const;
		virtual bool operator!=(const Table &other) const	{ return !(*this == other);  }
//@}
/** @name Serialization interface
 * @{
 ******************************************************************************/
	public:
		virtual std::string toString() const;
		virtual bool updateHash( DataHashContext* hash_context ) const;
//@}
/** @name Internal vars
 * @{
 ******************************************************************************/
	protected:
		std::string table_name;							///< name of the table

		unsigned int rows;								///< number of rows 
		unsigned int cols;								///< number of columns 
		mutable ISerializable** items;				///< array of items (raster scan)
		mutable ISerializable** headerVertical;	///< array vertical headers [rows]
		mutable ISerializable** headerHorizontal;	///< array horizontal headers [columns]

//@}


}; // class def

}; // namespace wosh

#endif //__WOSH_Core_Table_H__
